import React, { useRef, forwardRef, useImperativeHandle, isValidElement, useContext } from 'react';
import clsx from 'clsx';

import Loading from '../loading';
import { getRect } from '../hooks/use-rect';
import useScrollParent from '../hooks/use-scroll-parent';
import useEventListener from '../hooks/use-event-listener';

import { isHidden } from '../utils';
import { ListInstance, ListProps } from './PropsType';
import { useSetState, useUpdateEffect } from '../hooks';
import ConfigProviderContext from '../config-provider/ConfigProviderContext';
import useNamespace from '../utils/useNamespace';

const List = forwardRef<ListInstance, ListProps>((props, ref) => {
  const { locale } = useContext(ConfigProviderContext);
  const [bem] = useNamespace('list');
  const {
    loading,
    error,
    finished,
    finishedText,
    errorText,
    loadingText,
    offset = 300,
    direction = 'down',
    immediateCheck = true,
    autoCheck = true,
    className,
    style,
    children,
    onLoad,
  } = props;
  const [state, updateState] = useSetState({
    loading: Boolean(loading),
    error: Boolean(error),
  });

  const root = useRef<HTMLDivElement | null>(null);
  const scrollParent = useRef<Element | Window | null>(null);
  const placeholder = useRef<HTMLDivElement | null>(null);

  scrollParent.current = useScrollParent(root);

  // 判断是否需要加载
  const getElementRect = (element: Element | Window | null) =>
    element ? getRect(element) : getRect(window);

  const check = async () => {
    if (!onLoad) return;
    if (state.loading || finished || state.error) {
      return;
    }
    const resolvedOffset =
      typeof offset === 'number' ? offset : Number((offset as number | string) || 0);
    const parentElement = (scrollParent.current as Element | Window | null) ?? window;
    const scrollParentRect = getElementRect(parentElement);

    const currentRoot = root.current;
    if (!scrollParentRect.height || !currentRoot || isHidden(currentRoot)) {
      return;
    }

    let isReachEdge = false;
    const placeholderRect = getElementRect(placeholder.current ?? currentRoot);

    if (direction === 'up') {
      isReachEdge = scrollParentRect.top - placeholderRect.top <= resolvedOffset;
    } else {
      isReachEdge = placeholderRect.bottom - scrollParentRect.bottom <= resolvedOffset;
    }
    if (isReachEdge) {
      try {
        updateState({ loading: true });
        await onLoad();
        updateState({ loading: false });
      } catch (error) {
        // eslint-disable-next-line no-console
        console.warn('onLoad error:', error);
        updateState({ loading: false, error: true });
      }
    }
  };

  const renderFinishedText = () => {
    if (finished && finishedText) {
      return <div className={clsx(bem('finished-text'))}>{finishedText}</div>;
    }
    return null;
  };

  const clickErrorText = () => {
    updateState({ error: false });
    check();
  };

  const renderErrorText = () => {
    if (state.error && errorText) {
      return (
        <div className={clsx(bem('error-text'))} onClick={clickErrorText}>
          {errorText}
        </div>
      );
    }
    return null;
  };

  const renderLoading = () => {
    if (state.loading && !finished) {
      return (
        <div className={clsx(bem('loading'))}>
          {isValidElement(loadingText) ? (
            loadingText
          ) : (
            <Loading className={clsx(bem('loading-icon'))} size={16}>
              {loadingText || locale?.loading}
            </Loading>
          )}
        </div>
      );
    }
    return null;
  };

  useUpdateEffect(() => {
    if (autoCheck) {
      check();
    }
  }, [state.loading, finished, error]);

  useUpdateEffect(() => {
    updateState({ loading: Boolean(loading), error: Boolean(error) });
  }, [loading, error, updateState]);

  useUpdateEffect(() => {
    if (scrollParent.current && immediateCheck) {
      check();
    }
  }, [scrollParent.current]);

  useEventListener('scroll', check, {
    target: scrollParent.current ?? window,
    depends: [state.loading, finished, state.error],
  });

  useImperativeHandle(ref, () => ({
    check,
    state,
  }));

  const Placeholder = <div ref={placeholder} className={clsx(bem('placeholder'))} />;

  return (
    <div
      ref={root}
      role="feed"
      className={clsx(bem(), className)}
      style={style}
      aria-busy={state.loading}
    >
      {direction === 'down' ? children : Placeholder}
      {renderLoading()}
      {renderFinishedText()}
      {renderErrorText()}
      {direction === 'up' ? children : Placeholder}
    </div>
  );
});

export default List;
